package com.gitee.l0km.beanfilter.core.json;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Set;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.FieldDeserializer;
import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer;
import com.alibaba.fastjson.util.FieldInfo;
import com.alibaba.fastjson.util.TypeUtils;
import com.gitee.l0km.beanfilter.core.CodecPhase;
import com.gitee.l0km.beanfilter.core.CodecScene;
import com.gitee.l0km.beanfilter.core.Filterable;
import com.gitee.l0km.beanfilter.core.IFilter;
import com.gitee.l0km.beanfilter.core.LocalSupplyFilter;
import com.gitee.l0km.beanfilter.core.RequestPhase;
import com.gitee.l0km.beanfilter.core.SupplyFilter;
import com.gitee.l0km.beanfilter.core.context.LocalContext;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;

import static com.gitee.l0km.beanfilter.core.utils.CaseSupport.isSnakecase;
import static com.gitee.l0km.beanfilter.core.utils.CaseSupport.toCamelcase;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * dynamic field filter implementation of  FastJson {@link JavaBeanDeserializer},<br>
 * MAVEN dependencies:
 * <pre>
 *		&lt;dependency&gt;
 *			&lt;groupId&gt;com.alibaba&lt;/groupId&gt;
 *			&lt;artifactId&gt;fastjson&lt;/artifactId&gt;
 *			&lt;version&gt;${fastjson.version}&lt;/version&gt;
 *			&lt;scope&gt;provided&lt;/scope&gt;
 *		&lt;/dependency&gt;
 * </pre>
 * 
 * @author guyadong
 *
 */
public class FastjsonFilterableDeserializer extends JavaBeanDeserializer implements Filterable {
	private final boolean autoCase;
	private final LocalSupplyFilter localSupplyFilter;
	public FastjsonFilterableDeserializer(ParserConfig config, Class<?> beanClass, boolean autoCase) {
		super(config, checkNotNull(beanClass,"beanClass is null"));
		this.autoCase = autoCase;
		this.localSupplyFilter = new LocalSupplyFilter(beanClass, RequestPhase.DESERIALIZING, CodecScene.FASTJSON);
	}
	public FastjsonFilterableDeserializer(Class<?> beanClass, boolean autoCase) {
		this(ParserConfig.getGlobalInstance(), beanClass, autoCase);
	}
	protected SupplyFilter getSupplyFilter() {
		return localSupplyFilter.getSupplyFilter();
	}
	@Override
	public void injectFilter(Supplier<? extends IFilter> fieldFilterSupplier, Supplier<? extends IFilter> valueFilterSupplier, CodecPhase codecPhase){
		if(null != fieldFilterSupplier || null !=valueFilterSupplier) {
			SupplyFilter supplyFilter =  SupplyFilter.combine2(fieldFilterSupplier,valueFilterSupplier);
			localSupplyFilter.setSupplyFilter(supplyFilter);
		}
	}
	protected JSONObject beforeCreateInstance(JSONObject json) {
		return json;
	}
	protected <T>T afterDeserialize(JSONObject json, T instance, Set<String> modifiedReset, Set<String> modifiedSet) {
		return instance;
	}
	@SuppressWarnings("unchecked")
	@Override
	public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
		/** convert Map to target type based BaseBean  */
		try {
			LocalContext.enteringEnv(null, RequestPhase.DESERIALIZING, CodecScene.FASTJSON);
			/** deserialze to JSONObject */
			JSONObject json = (JSONObject)super.deserialze(parser, JSONObject.class, fieldName);
			if(autoCase) {
				// replace key to camel-case if snake-case
				for(String key:Lists.newArrayList(json.keySet()) ) {
					if(isSnakecase(key)) {
						String camelcase = toCamelcase(key);
						if(!json.containsKey(camelcase)) {
							json.put(camelcase, json.get(key));
							json.remove(key);
						}
					}
				}
			}
			/** 输入中存在的字段 */
			Set<String> deserialzied = json.keySet();
			/** 被过滤的字段 */
			LinkedHashSet<String> modifiedReset = new LinkedHashSet<>();
			/** 值过滤时被额外添加非null值的字段 */
			LinkedHashSet<String> modifiedSet = new LinkedHashSet<>();
			if(getSupplyFilter().getValueFilter()!=null) {
				for(String key: new ArrayList<>(json.keySet())) {
					Object value = json.get(key);
					FieldDeserializer deserializer = smartMatch(key);
					if(deserializer != null) {
						Object casted = TypeUtils.cast(value, deserializer.fieldInfo.fieldType, parser.getConfig());
						process(json,key,value,casted,deserializer,parser);
					}else {
						modifiedReset.add(key);
						json.remove(key);
					}
				}
			}
			processUndeserialized(json,deserialzied,modifiedSet, parser);
			return (T) afterDeserialize( json, createInstance(beforeCreateInstance(json), parser.getConfig()), modifiedReset, modifiedSet);
		} catch (Exception e) {
			throw new JSONException(e.getMessage(), e);
		}finally {
			LocalContext.exitingEnv();
		}
	}

	/**
	 * 执行值过滤，过滤后的值与原值不同返回{@code true}，否则返回{@code false}
	 */
	private boolean process(JSONObject json,
			String key,Object value, Object casted,
			FieldDeserializer deserializer,DefaultJSONParser parser) {
		Object processed = getSupplyFilter().process(clazz, key, casted);
		if(!Objects.equal(value, processed)) {
			/** 确保 IValueFilter 返回类型与字段类型匹配 */
			Object fieldValue = TypeUtils.cast(processed, deserializer.fieldInfo.fieldType, parser.getConfig());
			json.put(key, fieldValue);
			return true;
		}
		return false;
	}
	/**
	 * 对反序列化时不存在的参数字段进行值过滤
	 * @param json
	 * @param deserialized 输入中存在的字段
	 * @param additionalSet [out]保存不存在的参数字段进行值过滤返回为非{@code null}的值的字段
	 * @param parser
	 */
	protected void processUndeserialized(JSONObject json,Set<String> deserialized,Set<String> additionalSet, DefaultJSONParser parser) {
		if(getSupplyFilter().getValueFilter() != null) {
			for(FieldInfo info : beanInfo.sortedFields) {
				if(!deserialized.contains(info.name)) {
					FieldDeserializer deserializer = smartMatch(info.name);
					if(null != deserializer) {
						if(process(json,info.name,null,null,deserializer,parser)) {
							additionalSet.add(info.name);
						}
					}
				}
			}
		}
	}
	@Override
	public FieldDeserializer smartMatch(String key) {
		if(getSupplyFilter().permit(clazz, key)) {
			return super.smartMatch(key);
		}
		return null;
	}

}
